/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.storage.loading;

import java.nio.ByteBuffer;

import edu.ucsd.db.bassarisk.data.api.IValueType;
import edu.ucsd.db.bassarisk.storage.common.ColumnStore;

public class DrawerLoader<VT extends IValueType> {

  private final ByteBuffer drawer;
  private final ColumnSegmentInfo<VT> drawerInfo;
  private final IDrawerIndexer<VT> indexer;

  private int dataCardCount;

  public DrawerLoader(final IDrawerIndexer<VT> indexer,
                      final ColumnSegmentInfo<VT> drawerInfo, final ByteBuffer drawer) {
    this.dataCardCount = 0;

    this.drawer = drawer;
    assert this.drawer != null;
    assert this.drawer.array().length == ColumnStore.BYTES_IN_DRAWER;

    this.indexer = indexer;
    assert this.indexer != null;

    this.drawerInfo = drawerInfo;
    assert this.drawerInfo != null;
  }

  public boolean appendCard(final byte[] encodedCardBytes,
                            final ColumnSegmentInfo<VT> cardInfo) {
    if (this.dataCardCount == 0) {
      this.drawerInfo.setFirstValue(cardInfo.getFirstValue());
    }

    // Do we have enough room to add a card?
    int usedCardsInDrawer = this.dataCardCount + this.indexer.cardsRequired();
    boolean spaceForCard = ColumnStore.CARDS_IN_DRAWER >= (usedCardsInDrawer + 1);
    if (!spaceForCard) { return false; }

    // Do we have enough room to increase the size of the index by a card?
    boolean canIncreaseIndexSize =
        ColumnStore.CARDS_IN_DRAWER >= (usedCardsInDrawer + 2);
    boolean spaceToIndex = this.indexer.indexCard(cardInfo, canIncreaseIndexSize);
    if (!spaceToIndex) { return false; }

    this.drawerInfo.append(cardInfo);

    // Place card
    int cardOffset = this.dataCardCount * ColumnStore.BYTES_IN_CARD;
    System.arraycopy(encodedCardBytes, 0, this.drawer.array(), cardOffset,
                     ColumnStore.BYTES_IN_CARD);

    // Update object state to reflect added card
    this.dataCardCount++;

    return true;
  }

  public boolean fileDrawer(final ColumnLoader<VT> columnLoader) {
    if (this.dataCardCount == 0) { return true; }
    // TODO (nbales) - Currently, each logical drawer gets its own BufferCache
    // page. It would be better, in the future, for smaller logical drawers to
    // share BufferCache pages. Here, a non-full page should be looked up and
    // sent to write index.
    int indexCardCount = this.indexer.writeIndex(this.drawer, this.dataCardCount);

    return columnLoader.appendDrawer(this.drawer.array(), this.drawerInfo,
                                     this.dataCardCount, indexCardCount);
  }

  public void initialize() {
    this.reset(0);
  }

  public void reset(final int drawerStartEntry) {
    this.dataCardCount = 0;
    this.drawer.clear();
    this.indexer.reset();
    this.drawerInfo.reset(drawerStartEntry);
  }

}
